package com.norbsoft.typefacehelper;

import android.app.Activity;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * Helper class for setting custom typefaces to {@link android.view.View} and
 * {@link android.view.ViewGroup}. Supports String styling using {@link android.text.SpannableString}
 *
 * Applies typeface to all child views recursively.
 *
 * For detailed information and guide: https://github.com/jskierbi/android-typeface-helper
 *
 * @author Jakub Skierbiszewski
 */
public class TypefaceHelper {

	private static TypefaceCollection sDefaultTypefaceCollection;

	/**
	 * Initialize helper with typeface collection.
	 * This should be called inside {@link android.app.Application#onCreate()}
	 * @param typefaceCollection
	 */
	public static void init(TypefaceCollection typefaceCollection) {
		sDefaultTypefaceCollection = typefaceCollection;
	}

	/**
	 * Return spannable string with default typeface style (style: Typeface.NORMAL)
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param context to obtain string resource
	 * @param strResId string resource id, content
	 *
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been called
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(Context context, int strResId) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		return typeface(context.getString(strResId), sDefaultTypefaceCollection, Typeface.NORMAL);
	}

	/**
	 * Return spannable string with typeface in certain style
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param context to obtain string resource
	 * @param strResId string resource id, content
	 * @param style    Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC or Typeface.BOLD_ITALIC
	 *
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been calledC
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(Context context, int strResId, int style) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		return typeface(context.getString(strResId), sDefaultTypefaceCollection, style);
	}

	/**
	 * Return spannable string with typeface in certain style
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param sequence to typeface typeface to
	 * @param style    Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC or Typeface.BOLD_ITALIC
	 *
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been calledC
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(CharSequence sequence, int style) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		return typeface(sequence, sDefaultTypefaceCollection, style);
	}

	/**
	 * Return spannable string with typeface in certain style
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param context to obtain string resource
	 * @param strResId string resource id, content
	 * @param collection TypefaceCollection instance
	 * @param style    Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC or Typeface.BOLD_ITALIC
	 *
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(Context context, int strResId, TypefaceCollection collection, int style) {
		return typeface(context.getString(strResId), collection, style);
	}

	/**
	 * Return spannable string with default typeface style (style: Typeface.NORMAL)
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param sequence to typeface typeface to
	 *
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been called
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(CharSequence sequence) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		return typeface(sequence, sDefaultTypefaceCollection, Typeface.NORMAL);
	}

	/**
	 * Return spannable string with applied typeface in certain style
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param sequence    content
	 * @param typefaceCollection Collection of typefaces
	 * @param style     Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC or Typeface.BOLD_ITALIC
	 * @throws IllegalArgumentException when invalid style is passed
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(CharSequence sequence, TypefaceCollection typefaceCollection, int style) {
		checkTypefaceStyleThrowing(style);
		SpannableString s = new SpannableString(sequence);
		s.setSpan(new TypefaceSpan(typefaceCollection.getTypeface(style)), 0, s.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
		return s;
	}

	/**
	 * Return spannable string with default typeface style (style: Typeface.NORMAL)
	 * see: http://stackoverflow.com/questions/8607707/how-to-set-a-custom-font-in-the-actionbar-title
	 *
	 * @param sequence    content
	 * @param typefaceCollection Collection of typefaces
	 * @return SpannableString that can be used in TextView.setText() method
	 */
	public static SpannableString typeface(CharSequence sequence, TypefaceCollection typefaceCollection) {
		return typeface(sequence, typefaceCollection, Typeface.NORMAL);
	}

	/**
	 * Apply typefaces to main acitivty view (android.R.id.content).
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been called
	 * @param activity to typeface custom typefaces to
	 */
	public static void typeface(Activity activity) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		typeface(activity.findViewById(android.R.id.content), sDefaultTypefaceCollection);
	}

	/**
	 * Apply typefaces to view
	 * @throws IllegalStateException {@link #init(TypefaceCollection)} has not been called
	 * @param view to typeface custom typefaces to
	 */
	public static void typeface(View view) {
		if (sDefaultTypefaceCollection == null) {
			throw new IllegalStateException("Default typeface collection not initialized. Forgot to call init()?");
		}
		typeface(view, sDefaultTypefaceCollection);
	}

	/**
	 * Apply typefaces to main acitivty view (android.R.id.content).
	 * @param activity to typeface custom typefaces to
	 * @param typefaceCollection to obtain typefaces SparseArray.
	 */
	public static void typeface(Activity activity, TypefaceCollection typefaceCollection) {
		typeface(activity.findViewById(android.R.id.content), typefaceCollection);
	}

	/**
	 * Apply fonts from map to all children of view (or view itself)
	 *
	 * @param view view for which to typeface fonts
	 * @param typefaceCollection Collection of typefaces
	 */
	public static void typeface(View view, TypefaceCollection typefaceCollection) {

		if (view instanceof ViewGroup) {
			applyTypeface((ViewGroup) view, typefaceCollection);
		} else {
			applyForView(view, typefaceCollection);
		}
	}

	/**
	 * Check if typeface style int is one of:
	 * <ul>
	 *      <li>{@link android.graphics.Typeface#NORMAL}</li>
	 *      <li>{@link android.graphics.Typeface#BOLD}</li>
	 *      <li>{@link android.graphics.Typeface#ITALIC}</li>
	 *      <li>{@link android.graphics.Typeface#BOLD_ITALIC}</li>
	 * </ul>
	 * @param style
	 */
	private static void checkTypefaceStyleThrowing(int style) {
		switch (style) {
		case Typeface.NORMAL:
		case Typeface.BOLD:
		case Typeface.ITALIC:
		case Typeface.BOLD_ITALIC:
			break;
		default:
			throw new IllegalArgumentException("Style have to be in (Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC)");
		}
	}

	/**
	 * Apply typeface to all ViewGroup childs
	 *
	 * @param viewGroup to typeface typeface
	 * @param typefaceCollection typeface collection
	 */
	private static void applyTypeface(ViewGroup viewGroup, TypefaceCollection typefaceCollection) {

		for (int i = 0; i < viewGroup.getChildCount(); i++) {
			View childView = viewGroup.getChildAt(i);
			if (childView instanceof ViewGroup) {
				applyTypeface((ViewGroup) childView, typefaceCollection);
			} else {
				applyForView(childView, typefaceCollection);
			}
		}
	}

	/**
	 * Apply typeface to single view
	 *
	 * @param view      to typeface typeface
	 * @param typefaceCollection typeface collection
	 */
	private static void applyForView(View view, TypefaceCollection typefaceCollection) {

		if (view instanceof TextView) {
			TextView textView = (TextView) view;
			Typeface oldTypeface = textView.getTypeface();
			final int style = oldTypeface == null ? Typeface.NORMAL : oldTypeface.getStyle();
			textView.setTypeface(typefaceCollection.getTypeface(style));
			textView.setPaintFlags(textView.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
		}
	}

	private static class TypefaceSpan extends MetricAffectingSpan {

		Typeface typeface;

		TypefaceSpan(Typeface typeface) {
			this.typeface = typeface;
		}

		@Override public void updateMeasureState(TextPaint p) {
			p.setTypeface(typeface);
			p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
		}

		@Override public void updateDrawState(TextPaint tp) {
			tp.setTypeface(typeface);
			tp.setFlags(tp.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
		}
	}
}
